package titiritero;

import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.Iterator;

import javax.swing.JOptionPane;

import controlador.ControladorAlgoTank;

import modelo.TanqueAlgo;
import modelo.Escenario;
import modelo.Juego;
import modelo.Objeto;
import modelo.ObservadorDeEscenario;

import titiritero.audio.Reproductor;
import vistas.VistaJuego;

/**
 * @author Nicolas
 * Esta clase es la encargada de manejar todo el gameloop. Basicamente tiene una lista
 * de ObjetosVivos y una Dibujables que son recorridas en cada gameloop.
 */
public class ControladorJuego implements ObservadorDeEscenario, Runnable {

	private long intervaloSimulacion;
	private Juego juego;
	private VistaJuego vistaJuego;
	private boolean estaEnEjecucion;
	private ArrayList<Dibujable> dibujables = new ArrayList<Dibujable>();
	private ArrayList<MouseClickObservador> mouseClickObservadores = new ArrayList<MouseClickObservador>();
	private ArrayList<KeyPressedObservador> keyPressedObservadores = new ArrayList<KeyPressedObservador>();
	private SuperficieDeDibujo superficieDeDibujo;
	private Reproductor reproductor;
	private Thread hiloAudio;
	private boolean estaReproductorActivo;
	private ControladorAlgoTank controladorJugador;

	public ControladorJuego(boolean activarReproductor, Juego juego, VistaJuego vistaJuego){
		this.juego = juego;
		this.vistaJuego = vistaJuego;

		this.estaReproductorActivo = activarReproductor;
		if(this.estaReproductorActivo)
			this.reproductor = new Reproductor();

		this.inicializar();
	}

	private void inicializar() {
	    this.setSuperficieDeDibujo(vistaJuego.getSuperficieDeDibujo());
		this.inicializarObjetosDibujables();
		this.setIntervaloSimulacion(20);
		
		TanqueAlgo algotank = this.juego.getNivelActual().getEscenario().getAlgoTank();
		Escenario escenario = this.juego.getNivelActual().getEscenario();

		controladorJugador = new ControladorAlgoTank(algotank);

		this.vistaJuego.getSuperficieDeDibujo().addKeyListener(controladorJugador);
		this.vistaJuego.agregarComienzoJuegoListener(new ComienzoJuegoListener(this));
		this.vistaJuego.agregarPausaJuegoListener(new PausaJuegoListener(this));
		escenario.agregarObservador(this);		
	}
	
	private void inicializarNuevoNivel() {
	    this.inicializarObjetosDibujables();
	}

	private void inicializarObjetosDibujables() {
	    this.dibujables.clear();
		ArrayList<Dibujable> dibujables = this.juego.getNivelActual().crearVistasDeObjetos();

		for (Dibujable d : dibujables) {
			this.dibujables.add(d);
		}

	}

	public boolean estaEnEjecucion(){
		return this.estaEnEjecucion;
	}

	public void comenzarJuego() {
		estaEnEjecucion = true;
		try {
			while(estaEnEjecucion) {
				simular();
				dibujar();
				Thread.sleep(intervaloSimulacion);
			    }
		    }
		catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public void comenzarJuegoAsyn() {
	    Thread thread = new Thread(this);
	    thread.start();
	    if(this.estaReproductorActivo) {
	        this.reproductor.encender();
	        this.hiloAudio = new Thread(this.reproductor);
	        this.hiloAudio.start();
	    }
	}

	/**
	 * Le da comienzo al juego poniendo en marcha el gameloop.
	 * @param cantidadDeCiclos cantidad de ciclos que debe correr el gameloop..
	 */
	public void comenzarJuego(int cantidadDeCiclos){
		int contador = 0;
		estaEnEjecucion = true;
		try{
			while(contador < cantidadDeCiclos && estaEnEjecucion){
				simular();
				dibujar();
				Thread.sleep(intervaloSimulacion);
				contador++;
			}
		}
		catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * Detiene el juego provocando la salida del gameloop.
	 */
	public void detenerJuego() {
		this.estaEnEjecucion = false;
		if(reproductor != null)
			this.reproductor.apagar();
	}

	public void agregarDibujable(Dibujable unDibujable) {
		dibujables.add(unDibujable);
	}

	public void removerDibujable(Dibujable unDibujable) {
		dibujables.remove(unDibujable);
	}

	public long getIntervaloSimulacion() {
		return intervaloSimulacion;
	}

	public void setIntervaloSimulacion(long intervaloSimulacion) {
		this.intervaloSimulacion = intervaloSimulacion;
	}

	private void dibujar() {
		@SuppressWarnings("unchecked")

		ArrayList<Dibujable> copiaDeDibujables = (ArrayList<Dibujable>) this.dibujables.clone();
		Iterator<Dibujable> iterador = copiaDeDibujables.iterator();
		while(iterador.hasNext()) {
			Dibujable dibujable = iterador.next();
			dibujable.dibujar(this.superficieDeDibujo);
		}
		this.superficieDeDibujo.actualizar();
	}

	/**
	 * Ejecuta la simulacion recorriendo la coleccion de objetivos vivos.
	 */
	private void simular() {
		this.superficieDeDibujo.limpiar();
		this.juego.vivir();
		
		if (this.juego.getNivelPasado()) {
		    this.detenerJuego();
		    JOptionPane.showMessageDialog(vistaJuego, "Nivel completado!");
		    this.juego.avanzarNivel();
		    this.inicializarNuevoNivel();
		    return;
		}
		
		if (this.juego.getJuegoHaTerminado()) {
		    this.detenerJuego();
		    JOptionPane.showMessageDialog(vistaJuego, "Ganaste!");
		    this.vistaJuego.setEnabled(false);
            this.vistaJuego.setVisible(false);
		}
		
		if (this.juego.getNivelPerdido()) {
		    this.detenerJuego();
		    int opcion = JOptionPane.showConfirmDialog(vistaJuego, "Perdiste el nivel! Empezamos de nuevo?", "Perdiste", 2);
		    if (opcion == JOptionPane.YES_OPTION) {
		        this.juego.restartNivel();
		        this.inicializarObjetosDibujables();
		        return;
		    } else {
		        this.vistaJuego.setEnabled(false);
		        this.vistaJuego.setVisible(false);
		    }
		}
		
		if (this.juego.getJuegoPerdido()) {
		    this.detenerJuego();
		    JOptionPane.showMessageDialog(vistaJuego, "Perdiste...");
            this.vistaJuego.setEnabled(false);
            this.vistaJuego.setVisible(false);
		}
	
	}

    public SuperficieDeDibujo getSuperficieDeDibujo() {
		return superficieDeDibujo;
	}

	public void setSuperficieDeDibujo(SuperficieDeDibujo superficieDeDibujo) {
		this.superficieDeDibujo = superficieDeDibujo;
	}

	/**
	 * Se encarga de derivar el manejo del evento click al objeto vista correspondiente
	 * @param x posicion horizontal del mouse
	 * @param y posicion vertial del mouse
	 */
	public void despacharMouseClick(int x, int y) {
		MouseClickObservador mouseClickObservador;
		Iterator<MouseClickObservador> iterador = this.mouseClickObservadores.iterator();
		while(iterador.hasNext()) {
			mouseClickObservador = iterador.next();
			mouseClickObservador.mouseClick(x, y);
		}
	}

	public void agregarMouseClickObservador(MouseClickObservador unMouseClickObservador){
		this.mouseClickObservadores.add(unMouseClickObservador);
	}

	public void removerMouseClickObservador(MouseClickObservador unMouseClickObservador) {
		this.mouseClickObservadores.remove(unMouseClickObservador);
	}

	/**
	 * Se encarga de derivar el manejo del evento keyPress al objeto vista correspondiente
	 * @param KeyEvent evento
	 */
	public void despacharKeyPress(KeyEvent event) {
		for (KeyPressedObservador observador : this.keyPressedObservadores) {
			observador.keyPressed(event);
		}
	}

	public void agregarKeyPressObservador(KeyPressedObservador unMouseClickObservador) {
		this.keyPressedObservadores.add(unMouseClickObservador);
	}

	public void removerKeyPressObservador(KeyPressedObservador unMouseClickObservador) {
		this.keyPressedObservadores.remove(unMouseClickObservador);
	}


	public Reproductor getReproductorDeAudio() {
		if(! this.estaReproductorActivo)
			throw new OperacionNoValida();
		return this.reproductor;
	}

	@Override
	public void notificarObjetoNuevo(Objeto unObjeto) {
		agregarDibujable(unObjeto.crearDibujable());
	}

	@Override
	public void notificarObjetoEliminado(Objeto unObjeto) {

		@SuppressWarnings("unchecked")
		ArrayList<Dibujable> copiaDeDibujables = (ArrayList<Dibujable>)this.dibujables.clone();
		Iterator<Dibujable> iterador = copiaDeDibujables.iterator();
		while(iterador.hasNext()) {
			Dibujable aRemover = iterador.next();
			if((aRemover.getPosicionable().getX() == unObjeto.getX()) && (aRemover.getPosicionable().getY() == unObjeto.getY())){
				removerDibujable(aRemover);
			}
		}
	}

    @Override
    public void run() {
        this.comenzarJuego();
    }

}
